﻿using System.ComponentModel;
using System.IO.Win32;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using Microsoft.Win32.SafeHandles;

namespace System.IO.LongPath
{
    public class LongPathFileAccessor : FileAccessorBase
    {
        private static FileStream MakeFileStream(string path,
                                                 FileMode mode,
                                                 FileAccess access,
                                                 FileShare share,
                                                 FileOptions options)
        {
            var handle = NativeMethods.CreateFile(path, (int) access, share, null, mode,
                                                  ((int) (options | (FileOptions) 1048576)), IntPtr.Zero);
            if (handle.IsInvalid) throw new Win32Exception(Marshal.GetLastWin32Error());
            return new FileStream(handle, access);
        }

        private static FileStream MakeFileStream(string path,
                                                 FileMode mode,
                                                 FileAccess access,
                                                 FileShare share,
                                                 FileOptions options,
                                                 int bufferSize)
        {
            var handle = NativeMethods.CreateFile(path, (int) access, share, null, mode,
                                                  ((int) (options | (FileOptions) 1048576)), IntPtr.Zero);
            if (handle.IsInvalid) throw new Win32Exception(Marshal.GetLastWin32Error());
            return new FileStream(handle, access, bufferSize);
        }


        public override void Copy(string sourceFileName, string destFileName, bool overwrite)
        {
            sourceFileName = LongPathTools.CheckAndNormalize(sourceFileName);
            destFileName = LongPathTools.CheckAndNormalize(destFileName);

            if (!NativeMethods.CopyFile(sourceFileName, destFileName, !overwrite))
                throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        public override Stream Create(string path, int bufferSize, FileOptions options, FileSecurity fileSecurity)
        {
            if (fileSecurity != null) throw new NotImplementedException();
            path = LongPathTools.CheckAndNormalize(path);
            return MakeFileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.Read, options, bufferSize);
        }

        public override void Delete(string path)
        {
            path = LongPathTools.CheckAndNormalize(path);
            NativeMethods.DeleteFile(path);
        }

        public override void Decrypt(string path)
        {
            throw new NotImplementedException();
        }

        public override void Encrypt(string path)
        {
            throw new NotImplementedException();
        }

        public override bool Exists(string path)
        {
            NativeMethods.WIN32_FIND_DATAW lpFindFileData;
            IntPtr firstFileW = NativeMethods.FindFirstFile(path, out lpFindFileData);
            if (firstFileW.ToInt32() == -1)
                return false;
            NativeMethods.FindClose(firstFileW);
            return true;
        }

        public override Stream Open(string path, FileMode mode, FileAccess access, FileShare share)
        {
            path = LongPathTools.CheckAndNormalize(path);
            return MakeFileStream(path, mode, access, share, FileOptions.None);

        }

        public static void SetFileTime(string path, DateTime? creationTimeUtc, DateTime? accessTimeUtc, DateTime? writeTimeUtc)
        {
            SafeFileHandle file = NativeMethods.CreateFile(path, 1073741824, FileShare.Read, null, FileMode.Open, 256 | 33554432, IntPtr.Zero);
            if (file.IsInvalid)
                throw new Win32Exception(Marshal.GetLastWin32Error());
            var lpCreationTime = new Runtime.InteropServices.ComTypes.FILETIME();
            var lpLastAccessTime = new Runtime.InteropServices.ComTypes.FILETIME();
            var lpLastWriteTime = new Runtime.InteropServices.ComTypes.FILETIME();
            if (creationTimeUtc != null)
                lpCreationTime = NativeMethods.ToFileTime(creationTimeUtc.Value);
            if (accessTimeUtc != null)
                lpLastAccessTime = NativeMethods.ToFileTime(accessTimeUtc.Value);
            if (writeTimeUtc != null)
                lpLastWriteTime = NativeMethods.ToFileTime(writeTimeUtc.Value);
            bool flag = NativeMethods.SetFileTime(file, ref lpCreationTime, ref lpLastAccessTime, ref lpLastWriteTime);
            file.Close();
            file.Dispose();
            if (!flag)
                throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        private static NativeMethods.WIN32_FIND_DATAW FindFile(string path)
        {
            NativeMethods.WIN32_FIND_DATAW lpFindFileData;
            IntPtr firstFileW = NativeMethods.FindFirstFile(path, out lpFindFileData);
            if (firstFileW.ToInt32() == 2 /*ERROR_FILE_NOT_FOUND*/)
                throw new FileNotFoundException();
            if (firstFileW.ToInt32() == -1)
                throw new Win32Exception(Marshal.GetLastWin32Error());
            NativeMethods.FindClose(firstFileW);
            return lpFindFileData;
        }

        public static void GetFileTime(string path, out DateTime creationTimeUtc, out DateTime accessTimeUtc,
                                       out DateTime writeTimeUtc)
        {
            var fileData = FindFile(path);
            creationTimeUtc = fileData.ftCreationTime.ToDateTime();
            accessTimeUtc = fileData.ftLastAccessTime.ToDateTime();
            writeTimeUtc = fileData.ftLastWriteTime.ToDateTime();
        }

        public override void SetCreationTimeUtc(string path, DateTime creationTimeUtc)
        {
            path = LongPathTools.CheckAndNormalize(path);
            SetFileTime(path, creationTimeUtc, null, null);
        }

        public override DateTime GetCreationTimeUtc(string path)
        {
            path = LongPathTools.CheckAndNormalize(path);
            DateTime creationTime;
            DateTime accessTime;
            DateTime writeTime;
            GetFileTime(path, out creationTime, out accessTime, out writeTime);
            return creationTime;
        }

        public override void SetLastAccessTimeUtc(string path, DateTime lastAccessTimeUtc)
        {
            path = LongPathTools.CheckAndNormalize(path);
            SetFileTime(path, null, lastAccessTimeUtc, null);
        }

        public override DateTime GetLastAccessTimeUtc(string path)
        {
            path = LongPathTools.CheckAndNormalize(path);
            DateTime creationTime;
            DateTime accessTime;
            DateTime writeTime;
            GetFileTime(path, out creationTime, out accessTime, out writeTime);
            return accessTime;
        }

        public override void SetLastWriteTimeUtc(string path, DateTime lastWriteTimeUtc)
        {
            path = LongPathTools.CheckAndNormalize(path);
            SetFileTime(path, null, null, lastWriteTimeUtc);
        }

        public override DateTime GetLastWriteTimeUtc(string path)
        {
            path = LongPathTools.CheckAndNormalize(path);
            DateTime creationTime;
            DateTime accessTime;
            DateTime writeTime;
            GetFileTime(path, out creationTime, out accessTime, out writeTime);
            return writeTime;
        }

        public override FileAttributes GetAttributes(string path)
        {
            var fileData = FindFile(path);
            return (FileAttributes) fileData.dwFileAttributes;
        }

        public override void SetAttributes(string path, FileAttributes fileAttributes)
        {
            path = LongPathTools.CheckAndNormalize(path);
            NativeMethods.SetFileAttributes(path, (uint) fileAttributes);
        }

        public override FileSecurity GetAccessControl(string path)
        {
            throw new NotImplementedException();
        }

        public override FileSecurity GetAccessControl(string path, AccessControlSections includeSections)
        {
            throw new NotImplementedException();
        }

        public override void SetAccessControl(string path, FileSecurity fileSecurity)
        {
            throw new NotImplementedException();
        }

        public override void Move(string sourceFileName, string destFileName)
        {
            sourceFileName = LongPathTools.CheckAndNormalize(sourceFileName);
            destFileName = LongPathTools.CheckAndNormalize(destFileName);
            if (!NativeMethods.MoveFile(sourceFileName, destFileName))
                throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        public override void Replace(string sourceFileName, string destinationFileName, string destinationBackupFileName,
                                     bool ignoreMetadataErrors)
        {
            sourceFileName = LongPathTools.CheckAndNormalize(sourceFileName);
            destinationFileName = LongPathTools.CheckAndNormalize(destinationFileName);
            destinationBackupFileName = LongPathTools.CheckAndNormalize(destinationBackupFileName);

            int dwReplaceFlags = 1;
            if (ignoreMetadataErrors)
                dwReplaceFlags |= 2;
            if (!NativeMethods.ReplaceFile(sourceFileName, destinationFileName, destinationBackupFileName, dwReplaceFlags, IntPtr.Zero, IntPtr.Zero))
                throw new Win32Exception(Marshal.GetLastWin32Error());
        }
    }
}